using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using EnvDTE80;
using EnvDTE;
using Koda.Resources;
using Koda.VisualStudio;
using Koda.Utils;

namespace Koda.UI
{
    public partial class CodeOutlineView : UserControl
    {
        private DTE2 dte;
        private WindowEvents windowEvents;
        private CodeModelEvents codeModelEvents;
        private bool hidePrivateMembers;
        private bool sortElements;
        private Dictionary<CodeElement, TreeNode> cache;
        private Document document;
        private Window parentWindow;
        private const string ClassName = "Class";
        private const string ClassPrivateName = "ClassPrivate";
        private const string FieldName = "Field";
        private const string FieldPrivateName = "FieldPrivate";
        private const string MethodName = "Method";
        private const string MethodPrivateName = "MethodPrivate";
        private const string PropertyName = "Property";
        private const string PropertyPrivateName = "PropertyPrivate";
        private const string DelegateName = "Delegate";
        private const string DelegatePrivateName = "DelegatePrivate";
        private const string ConstantName = "Constant";
        private const string ConstantPrivateName = "ConstantPrivate";
        private const string EnumName = "Enum";
        private const string EventName = "Event";
        private const string EventPrivateName = "EventPrivate";
        private const string NamespaceName = "Namespace";
        private const string UnknownName = "Unknown";
        private const string StructureName = "Structure";
        private const string StructurePrivateName = "StructurePrivate";
        private const string InterfaceName = "Interface";
        private const string InterfacePrivateName = "InterfacePrivate";

        public Window ParentWindow
        {
            get
            {
                return this.parentWindow;
            }
            set
            {
                this.parentWindow = value;
            }
        }

        public DTE2 DTE
        {
            get
            {
                return this.dte;
            }
            set
            {
                this.dte = value;
                Events2 events = (Events2)this.dte.Events;

                this.windowEvents = events.get_WindowEvents(null);
                this.windowEvents.WindowClosing += new _dispWindowEvents_WindowClosingEventHandler(this.windowEvents_WindowClosing);
                this.windowEvents.WindowActivated += new _dispWindowEvents_WindowActivatedEventHandler(this.windowEvents_WindowActivated);

                this.codeModelEvents = events.get_CodeModelEvents(null);
                this.codeModelEvents.ElementAdded += new _dispCodeModelEvents_ElementAddedEventHandler(this.codeModelEvents_ElementAdded);
                this.codeModelEvents.ElementChanged += new _dispCodeModelEvents_ElementChangedEventHandler(this.codeModelEvents_ElementChanged);
                this.codeModelEvents.ElementDeleted += new _dispCodeModelEvents_ElementDeletedEventHandler(this.codeModelEvents_ElementDeleted);

                if (this.dte.ActiveDocument != null)
                {
                    this.document = this.dte.ActiveDocument;
                    this.Synchronize(true);
                }
            }
        }

        public CodeOutlineView()
        {
            this.AutoScaleMode = AutoScaleMode.None;
            this.Font = SystemFonts.MessageBoxFont;
            this.AutoScaleMode = AutoScaleMode.Font;

            this.InitializeComponent();

            this.hidePrivateMembersToolStripButton.Image = Images.Lock_Private.ToBitmap();
            this.sortElementsToolStripButton.Image = Images.Sort.ToBitmap();
            this.cache = new Dictionary<CodeElement, TreeNode>();
            this.hidePrivateMembers = false;
            this.sortElements = false;
            this.imageList.Images.Add(CodeOutlineView.ClassName, Images.VSObject_Class);
            this.imageList.Images.Add(CodeOutlineView.ClassPrivateName, Images.VSObject_Class_Private);
            this.imageList.Images.Add(CodeOutlineView.FieldName, Images.VSObject_Field);
            this.imageList.Images.Add(CodeOutlineView.FieldPrivateName, Images.VSObject_Field_Private);
            this.imageList.Images.Add(CodeOutlineView.MethodName, Images.VSObject_Method);
            this.imageList.Images.Add(CodeOutlineView.MethodPrivateName, Images.VSObject_Method_Private);
            this.imageList.Images.Add(CodeOutlineView.PropertyName, Images.VSObject_Properties);
            this.imageList.Images.Add(CodeOutlineView.PropertyPrivateName, Images.VSObject_Properties_Private);
            this.imageList.Images.Add(CodeOutlineView.DelegateName, Images.VSObject_Delegate);
            this.imageList.Images.Add(CodeOutlineView.DelegatePrivateName, Images.VSObject_Delegate_Private);
            this.imageList.Images.Add(CodeOutlineView.ConstantName, Images.VSObject_Constant);
            this.imageList.Images.Add(CodeOutlineView.ConstantPrivateName, Images.VSObject_Constant_Private);
            this.imageList.Images.Add(CodeOutlineView.EnumName, Images.VSObject_Enum);
            this.imageList.Images.Add(CodeOutlineView.EventName, Images.VSObject_Event);
            this.imageList.Images.Add(CodeOutlineView.EventPrivateName, Images.VSObject_Event_Private);
            this.imageList.Images.Add(CodeOutlineView.NamespaceName, Images.VSObject_Namespace);
            this.imageList.Images.Add(CodeOutlineView.UnknownName, Images.VSObject_Unknown);
            this.imageList.Images.Add(CodeOutlineView.StructureName, Images.VSObject_Structure);
            this.imageList.Images.Add(CodeOutlineView.StructurePrivateName, Images.VSObject_Structure_Private);
            this.imageList.Images.Add(CodeOutlineView.InterfaceName, Images.VSObject_Interface);
            this.imageList.Images.Add(CodeOutlineView.InterfacePrivateName, Images.VSObject_Interface_Private);
        }

        private void codeModelEvents_ElementDeleted(object parent, CodeElement codeElement)
        {
            if (codeElement.ProjectItem.Document == this.document)
            {
                this.SynchronizeCodeElement(parent);
            }
        }

        private void codeModelEvents_ElementChanged(CodeElement codeElement, vsCMChangeKind Change)
        {
            if (Change == vsCMChangeKind.vsCMChangeKindRename)
            {
                if (codeElement.Kind == vsCMElement.vsCMElementParameter)
                    return;
            }
            else if (Change == vsCMChangeKind.vsCMChangeKindTypeRefChange)
            {
                if (codeElement.Kind == vsCMElement.vsCMElementFunction)
                    return;
            }

            if (codeElement.ProjectItem.Document == this.document)
            {
                this.SynchronizeCodeElement(codeElement.Collection.Parent);
            }
        }

        private void codeModelEvents_ElementAdded(CodeElement codeElement)
        {
            if (codeElement.ProjectItem.Document == this.document)
            {
                this.SynchronizeCodeElement(codeElement.Collection.Parent);
            }
        }

        private void windowEvents_WindowClosing(Window Window)
        {
            this.Reset();
        }

        private void windowEvents_WindowActivated(Window GotFocus, Window LostFocus)
        {
            if (GotFocus.Document != null && this.document != GotFocus.Document)
            {
                this.document = GotFocus.Document;
                this.Synchronize(true);
            }
        }

        private void codeTreeView_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && e.Clicks == 2)
            {
                TreeNode treeNode = this.codeTreeView.GetNodeAt(e.X, e.Y);
                if (treeNode != null)
                {
                    CodeElement codeElement = treeNode.Tag as CodeElement;
                    if (codeElement != null)
                    {
                        if (VsUtilities.ShowCodeElement(codeElement))
                        {
                            // TODO: Doesn't seem to work ...
                            this.document.ActiveWindow.Activate();
                            this.document.ActiveWindow.SetFocus();
                            this.document.Activate();
                        }
                    }
                }
            }

            ////Showing the context menu doesn't work very well. Commented out for now ...
            ////else if (e.Button == MouseButtons.Right && e.Clicks == 1)
            ////{

            ////    TreeNode treeNode = this.codeTreeView.GetNodeAt(e.X, e.Y);
            ////    if (treeNode != null)
            ////    {
            ////        CodeElement codeElement = treeNode.Tag as CodeElement;
            ////        if (codeElement != null)
            ////        {
            ////            if (VsUtilities.ShowCodeElement(codeElement))
            ////            {
            
            ////                Point point = this.PointToScreen(new Point(e.X, e.Y));
            ////                VsUtilities.ShowContextMenu(this.dte, "Code Window", point);
            ////            }
            ////        }
            ////    }
            ////}
        }

        private void hidePrivateMembersToolStripButton_Click(object sender, EventArgs e)
        {
            ToolStripButton button = (ToolStripButton)sender;
            if (this.hidePrivateMembers != button.Checked)
            {
                this.hidePrivateMembers = button.Checked;
                this.Synchronize(false);
            }
        }

        private void sortElementsToolStripButton_Click(object sender, EventArgs e)
        {
            ToolStripButton button = (ToolStripButton)sender;
            if (this.sortElements != button.Checked)
            {
                this.sortElements = button.Checked;
                this.Synchronize(true);
            }
        }

        private void Reset()
        {
            this.cache.Clear();
            this.codeTreeView.BeginUpdate();
            this.codeTreeView.Nodes.Clear();
            this.codeTreeView.EndUpdate();
        }

        public void Synchronize(bool reset)
        {
            try
            {
                this.Enabled = false;
                if (reset)
                {
                    this.Reset();
                }
                else
                {
                    this.cache.Clear();
                }
                this.codeTreeView.BeginUpdate();

                if (this.document != null)
                {
                    ProjectItem projectItem = this.document.ProjectItem;
                    if (projectItem != null)
                    {
                        FileCodeModel2 fileCodeModel = (FileCodeModel2)projectItem.FileCodeModel;
                        if (fileCodeModel != null)
                        {
                            this.Synchronize(this.codeTreeView.Nodes, fileCodeModel.CodeElements);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
            finally
            {
                this.codeTreeView.EndUpdate();
                this.Enabled = true;
            }
        }

        private void SynchronizeCodeElement(object obj)
        {
            if (obj is CodeElement)
            {
                CodeElement codeElement = (CodeElement)obj;
                if (this.cache.ContainsKey(codeElement))
                {
                    TreeNode treeNode = this.cache[codeElement];
                    this.codeTreeView.BeginUpdate();
                    this.Synchronize(treeNode.Nodes, VsCodeModelUtilities.GetCodeElementMembers(codeElement));
                    this.codeTreeView.EndUpdate();
                }
                else
                {
                    this.Synchronize(false);
                }
            }
            else
            {
                this.Synchronize(false);
            }
        }

        private void Synchronize(TreeNodeCollection nodes, CodeElements codeElements)
        {
            List<CodeElement> list = this.GetCodeElementsOfInterest(codeElements);

            if (nodes.Count > list.Count)
            {
                while (nodes.Count > list.Count)
                {
                    nodes.RemoveAt(nodes.Count - 1);
                }
            }
            else if (nodes.Count < list.Count)
            {
                while (nodes.Count < list.Count)
                {
                    nodes.Add(new TreeNode());
                }
            }

            for (int i = 0; i < list.Count; i++)
            {
                CodeElement codeElement = list[i];
                TreeNode node = nodes[i];

                this.UpdateTreeNode(node, codeElement);

                CodeElements children = VsCodeModelUtilities.GetCodeElementMembers(codeElement);
                if (children != null)
                {
                    this.Synchronize(node.Nodes, VsCodeModelUtilities.GetCodeElementMembers(codeElement));
                    node.Expand();
                }
            }
        }

        private void UpdateTreeNode(TreeNode node, CodeElement codeElement)
        {
            node.Tag = codeElement;

            string text = this.GetCodeElementText(codeElement);
            if (node.Text != text)
            {
                node.Text = text;
            }

            string imageKey = this.GetImageKey(codeElement);
            if (node.ImageKey != imageKey)
            {
                node.ImageKey = imageKey;
                node.SelectedImageKey = imageKey;
            }

            if (!this.cache.ContainsKey(codeElement))
            {
                this.cache.Add(codeElement, node);
            }
        }

        private string GetImageKey(CodeElement codeElement)
        {
            if (VsCodeModelUtilities.IsCodeElementPrivate(codeElement))
            {
                switch (codeElement.Kind)
                {
                    case vsCMElement.vsCMElementFunction:
                        return CodeOutlineView.MethodPrivateName;
                    case vsCMElement.vsCMElementProperty:
                        return CodeOutlineView.PropertyPrivateName;
                    case vsCMElement.vsCMElementVariable:
                        return CodeOutlineView.FieldPrivateName;
                    case vsCMElement.vsCMElementClass:
                        return CodeOutlineView.ClassPrivateName;
                    case vsCMElement.vsCMElementDelegate:
                        return CodeOutlineView.DelegatePrivateName;
                    case vsCMElement.vsCMElementEvent:
                        return CodeOutlineView.EventPrivateName;
                    case vsCMElement.vsCMElementInterface:
                        return CodeOutlineView.InterfacePrivateName;
                    case vsCMElement.vsCMElementStruct:
                        return CodeOutlineView.StructurePrivateName;
                }
            }
            else
            {
                switch (codeElement.Kind)
                {
                    case vsCMElement.vsCMElementFunction:
                        return CodeOutlineView.MethodName;
                    case vsCMElement.vsCMElementProperty:
                        return CodeOutlineView.PropertyName;
                    case vsCMElement.vsCMElementVariable:
                        return CodeOutlineView.FieldName;
                    case vsCMElement.vsCMElementClass:
                        return CodeOutlineView.ClassName;
                    case vsCMElement.vsCMElementDelegate:
                        return CodeOutlineView.DelegateName;
                    case vsCMElement.vsCMElementEvent:
                        return CodeOutlineView.EventName;
                    case vsCMElement.vsCMElementInterface:
                        return CodeOutlineView.InterfaceName;
                    case vsCMElement.vsCMElementStruct:
                        return CodeOutlineView.StructureName;
                    case vsCMElement.vsCMElementEnum:
                        return CodeOutlineView.EnumName;
                    case vsCMElement.vsCMElementNamespace:
                        return CodeOutlineView.NamespaceName;
                }
            }

            return CodeOutlineView.UnknownName;
        }

        private int SortElementsByName(CodeElement codeElement1, CodeElement codeElement2)
        {
            return codeElement1.Name.CompareTo(codeElement2.Name);
        }

        private List<CodeElement> GetCodeElementsOfInterest(CodeElements codeElements)
        {
            List<CodeElement> list = new List<CodeElement>();
            foreach (CodeElement codeElement in codeElements)
            {
                if (this.IsCodeElementOfInterest(codeElement))
                {
                    list.Add(codeElement);
                }
            }

            if (this.sortElements)
            {
                list.Sort(this.SortElementsByName);
            }
            return list;
        }

        private bool IsCodeElementOfInterest(CodeElement codeElement)
        {
            switch (codeElement.Kind)
            {
                case vsCMElement.vsCMElementVariable:
                case vsCMElement.vsCMElementFunction:
                case vsCMElement.vsCMElementProperty:
                case vsCMElement.vsCMElementClass:
                case vsCMElement.vsCMElementNamespace:
                case vsCMElement.vsCMElementDelegate:
                case vsCMElement.vsCMElementEvent:
                case vsCMElement.vsCMElementInterface:
                case vsCMElement.vsCMElementStruct:
                case vsCMElement.vsCMElementEnum:
                    if (this.hidePrivateMembers)
                    {
                        if (VsCodeModelUtilities.IsCodeElementPrivate(codeElement))
                        {
                            return false;
                        }
                    }
                    return true;
            }
            return false;
        }

        private string GetCodeElementText(CodeElement codeElement)
        {
            if (codeElement.Kind == vsCMElement.vsCMElementFunction)
            {
                CodeFunction2 codeFunction = (CodeFunction2)codeElement;
                return codeFunction.get_Prototype((int)vsCMPrototype.vsCMPrototypeParamTypes);
            }
            else
            {
                return codeElement.Name;
            }
        }
    }
}
